package com.zzyl.utils;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.zzyl.enums.BasicEnum;
import com.zzyl.exception.BaseException;
import com.zzyl.properties.WeChatProperties;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;

/**
 * 微信支付工具类
 */
@Component
public class WeChatPayUtil {
	
	//微信支付下单接口地址
	public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
	//申请退款接口地址
	public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
	//关闭交易单
	public static final String CLOSE_TRADING = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{}/close";
	//查询退款记录
	public static final String QUERY_REFUND = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/";
	//查询交易单
	public static final String QUERY_TRADING = " https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/";
	
	@Autowired
	private WeChatProperties weChatProperties;
	
	/**
	 * 获取调用微信接口的客户端工具对象
	 * @return
	 */
	private CloseableHttpClient getClient() {
		PrivateKey merchantPrivateKey;
		try {
			//merchantPrivateKey商户API私钥，如何加载商户API私钥请看常见问题
			merchantPrivateKey = PemUtil
					.loadPrivateKey(new ByteArrayInputStream(weChatProperties.getPrivateKey().getBytes(StandardCharsets.UTF_8)));
			
			// 获取证书管理器实例
			CertificatesManager certificatesManager = CertificatesManager.getInstance();
			// 向证书管理器增加需要自动更新平台证书的商户信息
			
			certificatesManager.putMerchant(weChatProperties.getMchId(),
					new WechatPay2Credentials(weChatProperties.getMchId(), new PrivateKeySigner(weChatProperties.getMchSerialNo(), merchantPrivateKey)),
					weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
			Verifier verifier = certificatesManager.getVerifier(weChatProperties.getMchId());
			
			// 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签
			return WechatPayHttpClientBuilder.create()
					.withMerchant(weChatProperties.getMchId(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
					.withValidator(new WechatPay2Validator(verifier))
					.build();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * 发送post方式请求
	 * @param url  请求路径
	 * @param body 请求体
	 * @return http响应结果
	 */
	private String post(String url, String body) throws Exception {
		CloseableHttpClient httpClient = getClient();
		
		HttpPost httpPost = new HttpPost(url);
		httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
		httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
		httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
		httpPost.setEntity(new StringEntity(body, "UTF-8"));
		
		CloseableHttpResponse response = httpClient.execute(httpPost);
		try {
			return EntityUtils.toString(response.getEntity());
		} finally {
			httpClient.close();
			response.close();
		}
	}
	
	/**
	 * 发送get方式请求
	 * @param url 请求路径
	 * @return http响应结果
	 */
	private String get(String url) throws Exception {
		CloseableHttpClient httpClient = getClient();
		
		HttpGet httpGet = new HttpGet(url);
		httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
		httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
		httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
		
		CloseableHttpResponse response = httpClient.execute(httpGet);
		try {
			return EntityUtils.toString(response.getEntity());
		} finally {
			httpClient.close();
			response.close();
		}
	}
	
	/**
	 * jsapi下单
	 * @param orderNum    商户订单号
	 * @param total       总金额，单位：元
	 * @param description 商品描述
	 * @param openid      微信用户的openid
	 * @return
	 */
	public String jsapi(String orderNum, BigDecimal total, String description, String openid) {
		JSONObject jsonObject = new JSONObject();
		jsonObject.put("appid", weChatProperties.getAppId());
		jsonObject.put("mchid", weChatProperties.getMchId());
		jsonObject.put("description", description);
		jsonObject.put("out_trade_no", orderNum);
		jsonObject.put("notify_url", weChatProperties.getNotifyUrl());
		
		JSONObject amount = new JSONObject();
		amount.put("total", NumberUtil.mul(NumberUtil.round(total, 2), 100).intValue());
		amount.put("currency", "CNY");
		
		jsonObject.put("amount", amount);
		
		JSONObject payer = new JSONObject();
		payer.put("openid", openid);
		
		jsonObject.put("payer", payer);
		
		String body = jsonObject.toJSONString();
		String jsonResult = "";
		try {
			jsonResult = post(JSAPI, body);
		} catch (Exception e) {
			throw new BaseException(BasicEnum.APPLY_TRADE_FAIL);
		}
		return jsonResult;
	}
	
	/***
	 *  查询交易结果
	 * @param outTradeNo 发起支付传递的交易单号
	 * @return 交易结果
	 */
	public String queryTrading(String outTradeNo) {
		//请求地址
		String url = QUERY_TRADING + outTradeNo + "?mchid=" + weChatProperties.getMchId();
		try {
			return get(url);
		} catch (Exception e) {
			throw new BaseException(BasicEnum.QUERY_TRADING_FAIL);
		}
		
	}
	
	/**
	 * 申请退款
	 * @param outTradeNo  商户订单号
	 * @param outRefundNo 商户退款单号
	 * @param refund      退款金额
	 * @param total       原订单金额
	 * @return 退款结果
	 */
	public String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) {
		JSONObject jsonObject = new JSONObject();
		jsonObject.put("out_trade_no", outTradeNo);
		jsonObject.put("out_refund_no", outRefundNo);
		
		JSONObject amount = new JSONObject();
		amount.put("refund", NumberUtil.mul(NumberUtil.round(refund, 2), 100).intValue());
		amount.put("total", NumberUtil.mul(NumberUtil.round(total, 2), 100).intValue());
		amount.put("currency", "CNY");
		jsonObject.put("amount", amount);
		
		String body = jsonObject.toJSONString();
		
		//调用申请退款接口
		try {
			return post(REFUNDS, body);
		} catch (Exception e) {
			throw new BaseException(BasicEnum.APPLY_REFUND_FAIL);
		}
	}
	
	/***
	 *  统一收单交易退款接口查询
	 * @param outRefundNo 商户系统内部的退款单号
	 * @return 退款信息
	 */
	public String queryRefund(String outRefundNo) {
		String url = QUERY_REFUND + outRefundNo;
		try {
			return get(url);
		} catch (Exception e) {
			throw new BaseException(BasicEnum.QUERY_REFUND_FAIL);
		}
	}
	
	/**
	 * 关闭交易单
	 * @param outTradeNo 商户订单号
	 * @return http响应结果
	 */
	public String closeTrading(String outTradeNo) throws Exception {
		//请求地址
		String url = StrUtil.format(CLOSE_TRADING, outTradeNo);
		JSONObject jsonObject = new JSONObject();
		jsonObject.put("mchid", weChatProperties.getMchId());
		
		String body = jsonObject.toJSONString();
		return post(url, body);
	}
	
	/**
	 * 生成
	 * @param timeStamp 时间戳
	 * @param nonceStr  随机数
	 * @param packages  预支付字符串
	 * @return 签名字符串
	 * @throws Exception 不处理异常，全部抛出
	 */
	public String createPaySign(Long timeStamp, String nonceStr, String packages) throws Exception {
		Signature sign = Signature.getInstance("SHA256withRSA");
		
		// 加载商户私钥
		PrivateKey privateKey = PemUtil
				.loadPrivateKey(new ByteArrayInputStream(weChatProperties.getPrivateKey().getBytes(CharsetUtil.CHARSET_UTF_8)));
		sign.initSign(privateKey);
		String message = StrUtil.format("{}\n{}\n{}\n{}\n",
				weChatProperties.getAppId(),
				timeStamp,
				nonceStr,
				packages);
		sign.update(message.getBytes());
		return Base64.getEncoder().encodeToString(sign.sign());
	}
}
